package org.apache.guacamole.auth.user;

import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.auth.TutorialGuacamoleProperties;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.Connection;
import org.apache.guacamole.net.auth.Credentials;
import org.apache.guacamole.net.auth.Directory;
import org.apache.guacamole.net.auth.User;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.simple.SimpleDirectory;
import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet;
import org.apache.guacamole.net.auth.simple.SimpleUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Service for deriving Guacamole extension API data from UserData objects.
 */
@Singleton
public class UserDataService {

    /**
     * Logger for this class.
     */
    private static final Logger logger = LoggerFactory.getLogger(UserDataService.class);


    /**
     * Provider for UserDataConnection instances.
     */
    @Inject
    private Provider<UserDataConnection> userDataConnectionProvider;

    /**
     * The name of the HTTP parameter from which base64-encoded, encrypted JSON
     * data should be read. The value of this parameter, when decoded and
     * decrypted, must be valid JSON prepended with the 32-byte raw binary
     * signature generated through signing the JSON with the secret key using
     * HMAC/SHA-256.
     */
    public static final String ENCRYPTED_DATA_PARAMETER = "data";

    /**
     * Derives a new UserData object from the data contained within the given
     * Credentials. If no such data is present, or the data present is invalid,
     * null is returned.
     *
     * @param credentials The Credentials from which the new UserData object should be
     *                    derived.
     * @return A new UserData object derived from the data contained within the
     * given Credentials, or null if no such data is present or if the data
     * present is invalid.
     */
    public UserData fromCredentials(Credentials credentials) throws GuacamoleException {

        var rq = credentials.getRequest();

        System.out.println(rq.getClass().getCanonicalName());

        var paraNames = rq.getParameterNames();
        while (paraNames.hasMoreElements()) {
            String thisName = paraNames.nextElement().toString();
            String thisValue = rq.getParameter(thisName);
            System.out.println(thisName + " = " + thisValue);
        }


        var key = rq.getParameter("key");
        System.out.println("key_: " + key);
        var expires = rq.getParameter("expires");
        System.out.println("expires_: " + expires);
        try {
            if (key != null && expires != null && Integer.valueOf(expires) != null) {
                var jedis = new Jedis("127.0.0.1", 6379);
                jedis.connect();
                jedis.set("guest_password", key);
                jedis.expire("guest_password", Long.parseLong(expires));
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        // Get the Guacamole server environment
        Environment environment = new LocalEnvironment();
        String username = credentials.getUsername();

        if ("guest".equalsIgnoreCase(username)) {
            //访客，走访客的验证方式
            var jedis = new Jedis("127.0.0.1", 6379);
            jedis.connect();
            if (!jedis.exists("guest_password")) {
                return null;
            }
            var password = jedis.get("guest_password");
            System.out.println("guest_password: " + password);
            if (!password.equalsIgnoreCase(credentials.getPassword())) {
                return null;
            }
        } else {
            // Get username from guacamole.properties
            String username0 = environment.getRequiredProperty(
                    TutorialGuacamoleProperties.TUTORIAL_USER
            );

            // If wrong username, fail
            if (!username0.equals(username)) {
                return null;
            }

            // Get password from guacamole.properties
            String password = environment.getRequiredProperty(
                    TutorialGuacamoleProperties.TUTORIAL_PASSWORD
            );

            // If wrong password, fail
            if (!password.equals(credentials.getPassword())) {
                return null;
            }
        }


        UserData userData = new UserData();
        userData.setUsername(username);
        userData.setSingleUse(false);
        userData.setExpires(System.currentTimeMillis() * 2);





        // Successful login. Return configurations.
        ConcurrentMap<String, UserData.Connection> configs =
                new ConcurrentHashMap<String, UserData.Connection>();

        // Create new configuration
        UserData.Connection config = new UserData.Connection();

        config.setId("HOPE_BE_ONE");
        config.setPrimaryConnection(config.getId());

        // Set protocol specified in properties
        config.setProtocol(environment.getRequiredProperty(
                TutorialGuacamoleProperties.TUTORIAL_PROTOCOL
        ));

        // Set all parameters, splitting at commas
        for (String parameterValue : environment.getRequiredProperty(
                TutorialGuacamoleProperties.TUTORIAL_PARAMETERS
        ).split(",\\s*")) {

            // Find the equals sign
            int equals = parameterValue.indexOf('=');
            if (equals == -1) {
                throw new GuacamoleServerException("Required equals sign missing");
            }

            // Get name and value from parameter string
            String name = parameterValue.substring(0, equals);
            String value = parameterValue.substring(equals + 1);

            // Set parameter as specified
            config.getParameters().put(name, value);

        }

        configs.put("Archempower Connection", config);

        userData.setConnections(configs);


        return userData;

    }

    /**
     * Returns the identifiers of all users readable by the user whose data is
     * given by the provided UserData object. As users of the
     * JSONAuthenticationProvider can only see themselves, this will always
     * simply be a set of the user's own username.
     *
     * @param userData All data associated with the user whose accessible user identifiers
     *                 are being retrieved.
     * @return A set containing the identifiers of all users readable by the user
     * whose data is given by the provided UserData object.
     */
    public Set<String> getUserIdentifiers(UserData userData) {

        // Each user can only see themselves
        return Collections.singleton(userData.getUsername());

    }

    /**
     * Returns the user object of the user to whom the given UserData object
     * belongs.
     *
     * @param userData All data associated with the user whose own user object is being
     *                 retrieved.
     * @return The user object of the user to whom the given UserData object
     * belongs.
     */
    public User getUser(UserData userData) {

        // Build user object with READ access to all available data
        return new SimpleUser(userData.getUsername()) {

            @Override
            public ObjectPermissionSet getUserPermissions() throws GuacamoleException {
                return new SimpleObjectPermissionSet(getUserIdentifiers(userData));
            }

            @Override
            public ObjectPermissionSet getConnectionPermissions() throws GuacamoleException {
                return new SimpleObjectPermissionSet(getConnectionIdentifiers(userData));
            }

            @Override
            public ObjectPermissionSet getConnectionGroupPermissions() throws GuacamoleException {
                return new SimpleObjectPermissionSet(getConnectionGroupIdentifiers(userData));
            }

        };

    }

    /**
     * Returns the identifiers of all connections readable by the user whose
     * data is given by the provided UserData object. If the provided UserData
     * is not expired, this will be the set of all connection identifiers
     * within the UserData. If the UserData is expired, this will be an empty
     * set.
     *
     * @param userData All data associated with the user whose accessible connection
     *                 identifiers are being retrieved.
     * @return A set containing the identifiers of all connections readable by the
     * user whose data is given by the provided UserData object.
     */
    public Set<String> getConnectionIdentifiers(UserData userData) {

        // Do not return any connections if empty or expired
        Map<String, UserData.Connection> connections = userData.getConnections();
        if (connections == null || userData.isExpired())
            return Collections.<String>emptySet();

        // Return all available connection identifiers
        return connections.keySet();

    }

    /**
     * Returns a Directory containing all connections accessible by the user
     * whose data is given by the provided UserData object. If the given
     * UserData object is not expired, this Directory will contain absolutely
     * all connections defined within the given UserData. If the given UserData
     * object is expired, this Directory will be empty.
     *
     * @param userData All data associated with the user whose connection directory is
     *                 being retrieved.
     * @return A Directory containing all connections accessible by the user whose
     * data is given by the provided UserData object.
     */
    public Directory<Connection> getConnectionDirectory(UserData userData) {

        // Do not return any connections if empty or expired
        Map<String, UserData.Connection> connections = userData.getConnections();
        if (connections == null || userData.isExpired())
            return new SimpleDirectory<>();

        // Convert UserData.Connection objects to normal Connections
        Map<String, Connection> directoryContents = new HashMap<>();
        for (Map.Entry<String, UserData.Connection> entry : connections.entrySet()) {

            // Pull connection and associated identifier
            String identifier = entry.getKey();
            UserData.Connection connection = entry.getValue();

            // Create Guacamole connection containing the defined identifier
            // and parameters
            Connection guacConnection = userDataConnectionProvider.get().init(
                    userData,
                    identifier,
                    connection
            );

            // Add corresponding Connection to directory
            directoryContents.put(identifier, guacConnection);

        }

        return new SimpleDirectory<>(directoryContents);

    }

    /**
     * Returns the identifiers of all connection groups readable by the user
     * whose data is given by the provided UserData object. This will always be
     * a set containing only the root connection group identifier. The
     * JSONAuthenticationProvider does not define any other connection groups.
     *
     * @param userData All data associated with the user whose accessible connection group
     *                 identifiers are being retrieved.
     * @return A set containing the identifiers of all connection groups readable
     * by the user whose data is given by the provided UserData object.
     */
    public Set<String> getConnectionGroupIdentifiers(UserData userData) {

        // The only connection group available is the root group
        return Collections.singleton(UserContext.ROOT_CONNECTION_GROUP);

    }

}
